/***********************************************************************************************************************
* Copyright (c) 2020, China Mobile Communications Group Co.,Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 
* the License. You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* @file        opencc_utf16.c
*
* @brief       Other character coding types converte to UTF-16 coding or 
*              UTF-16 encoding converts to other character coding types.
*
* @revision
* Date         Author          Notes
* 2020-11-20   OneOS Team      First Version
***********************************************************************************************************************
*/
#include "opencc_utf16.h"

static unsigned short cc_utf16BE_to_unicode_handle(const unsigned char *pin)
{
    return (pin[0] << 8) | pin[1];
}
static unsigned short cc_utf16LE_to_unicode_handle(const unsigned char *pin)
{
    return (pin[1] << 8) | pin[0];
}
static void cc_utf16BE_from_unicode_handle(unsigned long unicode, unsigned char *pout)
{
    *pout++ = (unicode >> 8) & 0xff;
    *pout = unicode & 0xff;
    return;
}
static void cc_utf16LE_from_unicode_handle(unsigned long unicode, unsigned char *pout)
{
    *pout++ = unicode & 0xff;
    *pout = (unicode >> 8) & 0xff;
    return;
}

static int cc_utf16_to_unicode(const void *in, unsigned size, unsigned long *val, 
            unsigned short (*handle)(const unsigned char *pin))
{
    const unsigned char *pin = (const unsigned char *)in;
    unsigned short ch1 = 0;
    unsigned short ch2 = 0;
    
    if (size < 2)
    {
        *val = 0;
        return size;
    }
    
    ch1 = handle(pin);
    /* 0xD800 ~ 0xDFFF is the agent area, https://unicode-table.com/cn/blocks/ */
    if ((ch1 < 0xd800) || (ch1 >= 0xe000 && ch1 <= 0xFFFF))
    {
        *val = ch1;
        return 2;
    }

    /* must be in 0xd800~0xdc00 */
    if (!(ch1 >= 0xd800 && ch1 < 0xdc00))
    {
        *val = 0xffffffff;
        return 2;
    }
    
    if (size < 4) // not enough for valid character
    {
        *val = 0;
        return size;
    }

    /* must be in 0xdc00~0xe000 */
    ch2 = handle(pin + 2);
    if (!(ch2 >= 0xdc00 && ch2 < 0xe000))
    {
        *val = 0xffffffff;
        return 2;
    }

    *val = ((ch1 - 0xd800) << 10) | (ch2 - 0xdc00);
    return 4;
}

static int cc_utf16_from_unicode(unsigned long val, void *out, unsigned size, 
            void (*handle)(unsigned long unicode, unsigned char *pout))
{
    unsigned char *pout = (unsigned char *)out;
    unsigned short ch1 = 0;
    unsigned short ch2 = 0;

    /* 0xd800 ~  0xdfff is the agent area, start from 0xF0000 is the supplementary Private Use Area */
    if ((val >= 0xd800 && val < 0xe000) || (val >= 0xF0000))
    {
        return -1;
    }
    
    if (val < 0xd800 || (val >= 0xe000 && val < 0x10000))
    {
        if (size >= 2)
        {
            handle(val, pout);
        }
        return 2;
    }

    ch1 = 0xd800 + ((val >> 10) & 0x3ff);
    ch2 = 0xdc00 + (val & 0x3ff);
    if (size >= 4)
    {
        handle(ch1, pout);
        handle(ch2, pout + 2);
    }
    return 4;
}


static int cc_utf16LE_to_unicode(const void *in, unsigned size, unsigned long *val)
{
    return cc_utf16_to_unicode(in, size, val, cc_utf16LE_to_unicode_handle);
}

static int cc_utf16LE_from_unicode(unsigned long val, void *out, unsigned size)
{
    return cc_utf16_from_unicode(val, out, size, cc_utf16LE_from_unicode_handle);
}

static int cc_utf16BE_to_unicode(const void *in, unsigned size, unsigned long *val)
{
    return cc_utf16_to_unicode(in, size, val, cc_utf16BE_to_unicode_handle);
}

static int cc_utf16BE_from_unicode(unsigned long val, void *out, unsigned size)
{
    return cc_utf16_from_unicode(val, out, size, cc_utf16BE_from_unicode_handle);
}

static cc_charc_set_t cc_utf16BE_set_entry = {
    .charc_type = CC_CHARC_TYPE_UTF16_BE,
    .min = 2,
    .max = 4,
    .unknown_char = '?',
    .charc_set_to_unicode = cc_utf16BE_to_unicode,
    .unicode_to_charc_set = cc_utf16BE_from_unicode,
};

static cc_charc_set_t cc_utf16LE_set_entry = {
    .charc_type = CC_CHARC_TYPE_UTF16_LE,
    .min = 2,
    .max = 4,
    .unknown_char = '?',
    .charc_set_to_unicode = cc_utf16LE_to_unicode,
    .unicode_to_charc_set = cc_utf16LE_from_unicode,
};

cc_charc_set_t *cc_utf16LE_get_entry(void)
{
    return &cc_utf16LE_set_entry;
}

cc_charc_set_t *cc_utf16BE_get_entry(void)
{
    return &cc_utf16BE_set_entry;
}


